home *** CD-ROM | disk | FTP | other *** search
/ Windows News 2010 Summer - Disc 1 / WN_Ete2010_CD1.iso / Onglet5 / Weezo / Weezo setup.exe / {code_appDir} / www / local / UPnPMapping.php < prev    next >
PHP Script  |  2010-05-19  |  16KB  |  433 lines

  1. <?php
  2. /**
  3.  * UPnP NAT script
  4.  *
  5.  *
  6.  * PHP version 5
  7.  *
  8.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  9.  * that is available through the world-wide-web at the following URI:
  10.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  11.  * the PHP License and are unable to obtain it through the web, please
  12.  * send a note to license@php.net so we can mail you a copy immediately.
  13.  *
  14.  * @category   NA
  15.  * @package    NA
  16.  * @author     Nicolas Bruley / Peer 2 World <contact@weezo.net>
  17.  * @copyright  2005-2008 Nicolas Bruley / Peer 2 World
  18.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  19.  * @version    CVS: $Id:$
  20.  * @link       http://www.weezo.net
  21.  * @since      File available since Release 1.1.2
  22.  */
  23.  
  24.  
  25.  
  26. /**
  27.  * @desc UPnP device
  28.  *
  29.  */
  30. class UPnPDevice{
  31.     private $serviceType;
  32.     private $SCPDURL;
  33.     private $controlURL;
  34.     private $friendlyName;
  35.  
  36.     function __construct($serviceType, $SCPDURL, $controlURL, $friendlyName){
  37.         $this->serviceType=$serviceType;
  38.         $this->SCPDURL=$SCPDURL;
  39.         $this->controlURL=$controlURL;
  40.         $this->friendlyName=$friendlyName;
  41.     }
  42.  
  43.     function debug($out=false){
  44.         cfAppendTextToFile('Device name : '.$this->friendlyName);
  45.         cfAppendTextToFile('Service type : '.$this->serviceType);
  46.         cfAppendTextToFile('SCPD URL : '.$this->SCPDURL);
  47.         cfAppendTextToFile('Control URL : '.$this->SCPDURL);
  48.     }
  49.  
  50.     /**
  51.      * @desc Create a new port mapping
  52.      *
  53.      * @param integer $port : port number
  54.      * @param string $protocol : protocol ("UDP" or "TCP")
  55.      * @param string $description : Friendly name of port mapping
  56.      * @return boolean : true if success, false if failed
  57.      */
  58.     function addPortMapping($port, $protocol, $lanIP, $description){
  59.  
  60.         if(!is_numeric($port) || $port>65536 || $port<1 || ($protocol!='TCP' && $protocol!='UDP' && $protocol!='both')) {
  61.             trigger_error('Invalid port number or protocol', E_WARNING);
  62.             return false;
  63.         }
  64.  
  65.         // TCP and UDP port mapping
  66.         if($protocol=='both')
  67.             return $this->addPortMapping($port,'TCP',$lanIP,$description) && $this->addPortMapping($port,'UDP',$lanIP,$description);
  68.  
  69.         /*
  70.         $msg ="<NewRemoteHost></NewRemoteHost>\n";
  71.         $msg.="<NewExternalPort>".$port."</NewExternalPort>\n";
  72.         $msg.="<NewProtocol>".$protocol."</NewProtocol>\n";
  73.         $msg.="<NewInternalPort>".$port."</NewInternalPort>\n";
  74.         $msg.="<NewInternalClient>".$lanIP."</NewInternalClient>\n";
  75.         $msg.="<NewEnabled>1</NewEnabled>\n";
  76.         $msg.="<NewPortMappingDescription>".$description."</NewPortMappingDescription>\n";
  77.         $msg.="<NewLeaseDuration>0</NewLeaseDuration>\n";
  78.         */
  79.  
  80.         // Single protocol port mapping
  81.  
  82.         $msg ="<NewRemoteHost xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\"></NewRemoteHost>\n";
  83.         $msg.="<NewExternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">".$port."</NewExternalPort>\n";
  84.         $msg.="<NewProtocol xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">".$protocol."</NewProtocol>\n";
  85.         $msg.="<NewInternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">".$port."</NewInternalPort>\n";
  86.         $msg.="<NewInternalClient xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">".$lanIP."</NewInternalClient>\n";
  87.         $msg.="<NewEnabled xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"boolean\">1</NewEnabled>\n";
  88.         $msg.="<NewPortMappingDescription xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">".$description."</NewPortMappingDescription>\n";
  89.         $msg.="<NewLeaseDuration xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui4\">0</NewLeaseDuration>\n";
  90.  
  91.         return $this->postAction('AddPortMapping', $msg);
  92.     }
  93.  
  94.     /**
  95.      * @desc Delete mapped port
  96.      *
  97.      * @param integer $port : port number
  98.      * @param string $protocol : protocol ("UDP" or "TCP")
  99.      * @return boolean : true if success, false if failed
  100.      */
  101.     function deletePortMapping($port, $protocol){
  102.         if(!is_numeric($port) || $port>65536 || $port<1 || ($protocol!='TCP' && $protocol!='UDP' && $protocol!='both')) {
  103.             trigger_error('Invalid port number or protocol', E_WARNING);
  104.             return false;
  105.         }
  106.  
  107.         // TCP and UDP port mapping deletion
  108.         if($protocol=='both') return $this->deletePortMapping($port,'TCP') && $this->deletePortMapping($port,'UDP');
  109.  
  110.         // Single protocol port mapping deletion
  111.         $msg ="<NewRemoteHost xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\"></NewRemoteHost>\n";
  112.         $msg.="<NewExternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">".$port."</NewExternalPort>\n";
  113.         $msg.="<NewProtocol xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">".$protocol."</NewProtocol>\n";
  114.  
  115.         return $this->postAction('DeletePortMapping', $msg);
  116.     }
  117.  
  118.     /**
  119.      * @desc return external IP adress
  120.      *
  121.      * @return unknown
  122.      */
  123.     function getExternalIPAddress(){
  124.         if(!$body=$this->postAction('GetExternalIPAddress', '')) return false;
  125.         $xml=new DOMDocument();
  126.         if(!@$xml->loadXML($body)) return false;
  127.  
  128.     }
  129.  
  130.     /**
  131.      * @desc send a POST request to device
  132.      *
  133.      * @param string $action : action (AddPortMapping, DeletePortMapping)
  134.      * @param string $msg : POST body
  135.      * @return boolean result
  136.      */
  137.     function postAction($action, $msg){
  138.         $cnx=parse_url($this->controlURL);
  139.  
  140.         // SOAP message
  141.         $message ='<?xml version="1.0"?>'."\r\n";
  142.         //$message.='<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'."\n";
  143.         //$message.="<s:Body>\n<u:".$action." xmlns:u=\"".$this->serviceType."\">\n";
  144.         $message='<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'."\n";
  145.         $message.="<SOAP-ENV:Body>\n<m:".$action." xmlns:m=\"".$this->serviceType."\">\n";
  146.  
  147.         $message.=$msg;
  148.  
  149.         //$message.="</u:".$action.">\n";
  150.         //$message.="</s:Body>\n</s:Envelope>";
  151.         $message.="</m:".$action.">\n";
  152.         $message.="</SOAP-ENV:Body>\n</SOAP-ENV:Envelope>\n";
  153.  
  154.  
  155.         $header ="POST ".$cnx['path'].((isset($cnx['query']))?'?'.$cnx['query']:'')." HTTP/1.1\r\n";
  156.         $header.="HOST: ".$cnx['host'].':'.$cnx['port']."\r\n";
  157.         $header.="CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
  158.         $header.="CONTENT-LENGTH: ".strlen($message)."\r\n";
  159.         $header.='SOAPACTION: "'.$this->serviceType.'#'.$action.'"'."\r\n";
  160.         $header.="\r\n";
  161.  
  162.         //cfVarDump($header.$message);
  163.  
  164.         // Open socket
  165.         if(!($handle = @fsockopen((($cnx['scheme']=='https')?'ssl://':'').$cnx['host'],$cnx['port'],$errno,$errstr))) return false;
  166.  
  167.         // POST message
  168.         fwrite($handle, $header.$message);
  169.  
  170.         // Get response
  171.         $response='';
  172.         /*while(!feof($handle))*/ $response.= fread($handle, 2048);
  173.         fclose($handle);
  174.  
  175.         if(!cfParseHTTPResponse($response,$statusCode,$body,$headers)) {
  176.             //cfVarDump('Modem response error');
  177.             //cfVarDump(str_replace('<','<',$response));
  178.             return false;
  179.         }
  180.  
  181.         // Method Not Allowed
  182.         if($statusCode==405){
  183.             // Rebuild MAN header
  184.             $header ="M-POST ".$cnx['path'].((isset($cnx['query']))?'?'.$cnx['query']:'')." HTTP/1.1\r\n";
  185.             $header.="HOST: ".$cnx['host'].':'.$cnx['port']."\r\n";
  186.             $header.="CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
  187.             $header.="CONTENT-LENGTH: ".strlen($message)."\r\n";
  188.             $header.="MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01\r\n";
  189.             $header.='01-SOAPACTION: "'.$this->serviceType.'#'.$action.'"'."\r\n";
  190.             $header.="\r\n";
  191.  
  192.             // Open socket
  193.             if(!($handle = @fsockopen((($cnx['scheme']=='https')?'ssl://':'').$cnx['host'],$cnx['port'],$errno,$errstr))) return false;
  194.  
  195.             // POST message
  196.             fwrite($handle, $header.$message);
  197.  
  198.             // Get response
  199.             $response='';
  200.             /*while(!feof($handle))*/ $response.= fread($handle, 2048);
  201.             fclose($handle);
  202.  
  203.             if(!cfParseHTTPResponse($response,$statusCode,$body,$headers)) return false;
  204.         }
  205.  
  206.         if($statusCode!=200) {
  207.             //echo "<b>ERROR</b>";
  208.             //cfVarDump(str_replace('<','<',$response));
  209.             return false;
  210.         }
  211.         //echo "<b>Success</b>";
  212.         //cfVarDump(str_replace('<','<',$response));
  213.         return true;
  214.     }
  215. }
  216.  
  217. /**
  218.  * @desc Get device from XML descriptor URL
  219.  *
  220.  * @param string $location : XML descriptor URL
  221.  * @return mixed UPnPDevice if ok, false if failed
  222.  */
  223. function UPnPGetDevice($location){
  224.     $url=parse_url($location);
  225.     if(!isset($url['host'])) return false;
  226.  
  227.     // Send request to $location URL to retreive XML devices descriptor
  228.     if(!$xml=cfSocketHTTPRequest($location,3)) return false;
  229.  
  230.     // Load XML
  231.     $dom=new DOMDocument();
  232.     $dom->loadXML($xml);
  233.  
  234.     // URLBase
  235.     if($dom->getElementsByTagName('URLBase')->length)  $URLBase=($dom->getElementsByTagName('URLBase')->item(0)->nodeValue); else $URLBase=false;
  236.  
  237.     // Device name
  238.     if($dom->getElementsByTagName('friendlyName')->length)  $friendlyName=($dom->getElementsByTagName('friendlyName')->item(0)->nodeValue); else $friendlyName=false;
  239.  
  240.     // Browse service (s) for WANIPConnection or WANPPPConnection
  241.     for($i=0;$i<$dom->getElementsByTagName('service')->length;$i++){
  242.         $node=$dom->getElementsByTagName('service')->item($i);
  243.         if(!$node->getElementsByTagName('serviceType')->length) continue;
  244.         $serviceType=strtolower($node->getElementsByTagName('serviceType')->item(0)->nodeValue);
  245.  
  246.         // Get service type
  247.         if($serviceType!='urn:schemas-upnp-org:service:wanipconnection:1' && $serviceType!='urn:schemas-upnp-org:service:wanpppconnection:1') continue;
  248.         $serviceType=($node->getElementsByTagName('serviceType')->item(0)->nodeValue);
  249.  
  250.         // SCPDURL
  251.         if(!$node->getElementsByTagName('SCPDURL')->length) continue; else $SCPDURL=($node->getElementsByTagName('SCPDURL')->item(0)->nodeValue);
  252.  
  253.         // controlURL
  254.         if(!$node->getElementsByTagName('controlURL')->length) continue; else $controlURL=($node->getElementsByTagName('controlURL')->item(0)->nodeValue);
  255.  
  256.         break;
  257.     }
  258.  
  259.     if(!isset($controlURL)) return false;
  260.  
  261.     // transform path into full URL
  262.     if(!$URLBase) $URLBase=((isset($url['scheme'])?$url['scheme']:'http')).'://'.$url['host'].':'.((isset($url['port'])?$url['port']:'80')).'/';
  263.     if(substr($URLBase,-1)!='/') $URLBase.='/';
  264.     if(strtolower(substr($controlURL,0,4))!='http') $controlURL=$URLBase.(($controlURL[0]=='/')?substr($controlURL,1):$controlURL);
  265.     if(strtolower(substr($SCPDURL,0,4))!='http') $SCPDURL=$URLBase.(($SCPDURL[0]=='/')?substr($SCPDURL,1):$SCPDURL);
  266.  
  267.     return new UPnPDevice($serviceType, $SCPDURL, $controlURL, $friendlyName);
  268. }
  269.  
  270. /**
  271.  * @desc parse UDP IGD discover response
  272.  *
  273.  * @param string $SSDPResponse
  274.  * @return string : descriptor location if found, null if LOCATION: not found
  275.  */
  276. function UPnPDeviceLocation($SSDPResponse){
  277.     if(!$SSDPResponse) return null;
  278.     $lines=explode("\n",str_replace("\r","\n",str_replace("\n\n","\n",$SSDPResponse)));
  279.     if(count($lines)<3) return null;
  280.     $resp=explode(' ',$lines[0]);
  281.  
  282.     // Check HTTP 200 OK
  283.     if(count($resp)!=3 || $resp[1]!=200 || strtoupper($resp[2])!='OK') return null;
  284.  
  285.     // Search for LOCATION:
  286.     foreach ($lines as $line){
  287.         $line=trim($line);
  288.         if(substr(strtolower($line),0,9)=='location:') return trim(substr($line,9));
  289.     }
  290.     return null;
  291. }
  292.  
  293. /**
  294.  * @desc Send UDP message to discover UPnP Internet Gateway Device
  295.  *
  296.  * @param integer $timeout : timeout for receiving message
  297.  * @return string : IGD descriptor URL if found or null if not found
  298.  */
  299. function UPnPFindDevice($timeout=3){
  300.     $mcast_addr='239.255.255.250'; $mcast_port=1900;
  301.  
  302.     $mcast_msg ="M-SEARCH * HTTP/1.1\r\n";
  303.     $mcast_msg.="Host:239.255.255.250:1900\r\n";
  304.  
  305.     //$mcast_msg.="ST:urn:schemas-UPnP-org:device:WANConnectionDevice:1\r\n"; // Whireshark
  306.     //$mcast_msg.="ST:UPnP:rootdevice\r\n"; // zbowling.com
  307.     $mcast_msg.="ST:urn:schemas-UPnP-org:device:InternetGatewayDevice:1\r\n"; // Python
  308.  
  309.     $mcast_msg.="Man:\"ssdp:discover\"\r\n";
  310.     $mcast_msg.="MX:3\r\n";
  311.     $mcast_msg.="\r\n";
  312.  
  313.     $mcast_2ndMessageDelay=0.5;
  314.     $secondMessageSent=false;
  315.  
  316.  
  317.     $sock=socket_create(AF_INET,SOCK_DGRAM,SOL_UDP);
  318.     socket_sendto($sock, $mcast_msg, strlen($mcast_msg), false, $mcast_addr, $mcast_port);
  319.     $location=null;
  320.     socket_set_nonblock($sock);
  321.     $i=0;
  322.     while($i<$timeout*100){  // the first non-full packet is the last.
  323.         $buffer='';
  324.  
  325.         // Send the message a 2nd time
  326.         if(!$secondMessageSent && $i>$mcast_2ndMessageDelay*100) {
  327.             socket_sendto($sock, $mcast_msg, strlen($mcast_msg), false, $mcast_addr, $mcast_port);
  328.             $secondMessageSent=true;
  329.         }
  330.         // Receive data
  331.         @socket_recvfrom($sock, $buffer, 516, 0, $addr, $port);
  332.         //cfDbg($buffer,1);
  333.         if($location=UPnPDeviceLocation($buffer)) {
  334.             socket_close($sock);
  335.             return $location;
  336.         }
  337.  
  338.         usleep(10000);
  339.         $i++;
  340.     }
  341.     socket_close($sock);
  342. }
  343.  
  344. /*
  345.  ***************************************************************************************************************************
  346.  * Process GET commands
  347.  ***************************************************************************************************************************
  348.  */
  349. if(isset($_GET['action'])){
  350.  
  351.     ignore_user_abort(true);
  352.  
  353.     if(true || !cfMIssetVar('weezoUPnPDevice')){
  354.         if(!$location=UPnPFindDevice(3)) die('nok no UPnP devices found');
  355.         if(!($device=UPnPGetDevice($location))) die('nok error reading device location');
  356.         //cfMSetVar('weezoUPnPDevice',$device);
  357.     }
  358.     else $device=cfMGetVar('weezoUPnPDevice');
  359.  
  360.     //$device->debug(1);
  361.  
  362.     /**
  363.      * New port mapping
  364.      */
  365.     if($_GET['action']=='addPortMapping'){
  366.         if(!isset($_GET['port']) || !isset($_GET['protocol']) || !isset($_GET['description']) || !isset($_GET['lanIP'])) die('nok incorrect parameters');
  367.  
  368.         // Multiple ports mapping
  369.         if(is_array($_GET['port'])){
  370.             if(!is_array($_GET['protocol']) || !is_array($_GET['description']) || count($_GET['protocol'])!=count($_GET['port']) || count($_GET['description'])!=count($_GET['port'])) die('nok incorrect parameters');
  371.             $nb=0;
  372.             foreach ($_GET['port'] as $key=>$port) {
  373.                 if($device->addPortMapping($port, $_GET['protocol'][$key],$_GET['lanIP'],$_GET['description'][$key])) $nb++;
  374.             }
  375.             if($nb==count($_GET['port'])) die('ok'); else die('nok '.$nb.' port(s) mapped');
  376.         }
  377.         // Single port mapping
  378.         else{
  379.  
  380.             if($device->addPortMapping($_GET['port'],$_GET['protocol'],$_GET['lanIP'],$_GET['description'])) die('ok'); else die('nok port mapping unsuccessfull');
  381.         }
  382.     }
  383.  
  384.     /**
  385.      * Delete port mapping
  386.      */
  387.     elseif($_GET['action']=='deletePortMapping'){
  388.         if(!isset($_GET['port']) || !isset($_GET['protocol'])) die('nok incorrect parameters');
  389.  
  390.         // Multiple ports deletion
  391.         if(is_array($_GET['port'])){
  392.             if(!is_array($_GET['protocol']) || count($_GET['protocol'])!=count($_GET['port'])) die('nok incorrect parameters');
  393.             $nb=0;
  394.             foreach ($_GET['port'] as $key=>$port) {
  395.                 if($device->deletePortMapping($port, $_GET['protocol'][$key])) $nb++;
  396.             }
  397.             if($nb>0) die('ok'); else die('nok');
  398.         }
  399.         // Single port mapping deletion
  400.         else{
  401.             if($device->deletePortMapping($_GET['port'],$_GET['protocol'])) die('ok'); else die('nok port mapping deletion unsuccessfull');
  402.         }
  403.     }
  404.  
  405.     /**
  406.      * Get external IP adress
  407.      */
  408.     elseif($_GET['action']=='getExternalIPAddress'){
  409.         $device->getExternalIPAddress();
  410.     }
  411. }
  412.  
  413.  
  414. /**
  415.  * Test form
  416.  */
  417. exit;
  418. ?>
  419. <form method="GET" name="UPnPForm">
  420. <input type="hidden" name="action">
  421. <input type="hidden" name="protocol" value="TCP">
  422. <input type="hidden" name="description" value="WeezoTest83">
  423. IP LAN <input type="text" name="lanIP" value="192.168.0.2" size="10"><br>
  424. Port (TCP) <input type="text" name="port" value="80" size="3"><br>
  425.  
  426. <input type="button" value="Ouvrir" onclick="document.forms.UPnPForm.action.value='addPortMapping';document.forms.UPnPForm.submit()">
  427. <input type="button" value="Fermer" onclick="document.forms.UPnPForm.action.value='deletePortMapping';document.forms.UPnPForm.submit()">
  428. <input type="button" value="Adresse IP externe" onclick="document.forms.UPnPForm.action.value='getExternalIPAddress';document.forms.UPnPForm.submit()">
  429. </form>
  430. <pre>
  431. <?php
  432. system('ipconfig');
  433. ?></pre>